﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Interfaces.CommonData;

[assembly: InternalsVisibleTo("TST_ALL")]

namespace StockInfoFacade.BusinessLogic
{
    internal class Correlation
    {
        private Decimal ComputeCoeff(Decimal[] values1, Decimal[] values2)
        {
            // TODO : the following is good to have, but the data hasn't been massaged
            //if (values1.Length != values2.Length)
            //    throw new ArgumentException("values must be the same length");

            // for now empty arrays have 0 validation
            if (values1.Length == 0 || values2.Length == 0)
                return 0;

            decimal avg1 = 0;
            decimal avg2 = 0;
            Task t1 = Task.Run(() => avg1 = values1.Average());
            Task t2 = Task.Run(() => avg2 = values2.Average());
            Task.WaitAll(t1, t2);

            var sum1 = values1.Zip(values2, (x1, y1) => (x1 - avg1) * (y1 - avg2)).Sum();

            double sumSqr1 = 0;
            double sumSqr2 = 0;
            t1 = Task.Run(() => sumSqr1 = values1.Sum(x => Math.Pow((double)(x - avg1), 2.0)));
            t2 = Task.Run(() => sumSqr2 = values2.Sum(y => Math.Pow((double)(y - avg2), 2.0)));
            Task.WaitAll(t1, t2);

            var result = sum1 / (Decimal)Math.Sqrt((double) sumSqr1 * sumSqr2);

            return result;
        }

        // make sure that AllStocks has prices provided in allStockPrices
        // Also the dates should be the same as well for all members
        public Dictionary<Stock, Dictionary<Stock, Decimal>> ComputeMatrix(List<StockPrice> allStockPrices, List<Stock> allStocks)
        {
            return ComputeMatrix(allStockPrices, allStocks, null);
        }

        public Dictionary<Stock, Dictionary<Stock, Decimal>> ComputeMatrix(List<StockPrice> allStockPrices, List<Stock> allStocks, BackgroundWorker bw)
        {
            Dictionary<Stock, Dictionary<Stock, Decimal>> corrMatrix = new Dictionary<Stock, Dictionary<Stock, decimal>>();

            int allStocksToLoad = allStocks.Count;
            int currentlyLoadedStocks = 0;

            //var qParallel = from s in allStocks.AsParallel()
            //                select s;

            // distribute by Stock ID
            allStocks.ForEach(s1 =>
            {
                Dictionary<Stock, decimal> insideDictionary = new Dictionary<Stock, decimal>();
                corrMatrix.Add(s1, insideDictionary);



                allStocks.ForEach(s2 =>
                {
                    Decimal corr = 0;
                    if (s1.Id == s2.Id)
                    {
                        corr = 1;
                        insideDictionary.Add(s2, 1);
                    }
                    else if (s1.Id < s2.Id)
                    {
                        var prices1 = from p in allStockPrices
                            orderby p.Date ascending
                            where p.StockId == s1.Id
                            select p.AdjPrice;

                        var prices2 = from p in allStockPrices
                            orderby p.Date ascending
                            where p.StockId == s2.Id
                            select p.AdjPrice;

                        corr = ComputeCoeff(prices1.ToArray(), prices2.ToArray());
                        insideDictionary.Add(s2, corr);
                    }
                    else // repeat symmetrical part
                        insideDictionary.Add(s2, corrMatrix[s2][s1]);
                });
                currentlyLoadedStocks++;
                if (bw != null)
                    bw.ReportProgress(100 * (currentlyLoadedStocks * 100) / (allStocksToLoad * 100));
                
            });

            return corrMatrix;
        }
    }
}
